// 基本数据类型
// 布尔值
let isBool: boolean = false; // 使用字面量创建，类型指定为boolean
// boolean是基本数据类型，Boolean是构造函数，实例化出的对象是Boolean类型
let createBoolean: Boolean = new Boolean(1);
// 但是可以使用Boolean直接来创建一个boolean类型
let isBoolean: boolean = Boolean(false)
console.log(isBool,createBoolean, isBoolean);


// 数字
// number为数据类型，Number是其包装类型
let isNumber: number = 10;
isNumber = 0b1010; // 二进制，结果为10
isNumber = 0o744; // 八进制，结果为484
isNumber = Infinity; // 无穷值，有正负
isNumber = NaN; // 非数字，isNaN()

// 字符串
// string为数据类型，String是其包装类型
let isStr: string = 'hello';
isStr = `hi ${isStr}`; // 模板字符串
console.log(isStr); // hi hello

// 空值
// JavaScript中没有空值的概念，但是typescript中
// 可使用void表示没有任何返回值的函
function voidFuc(): void{}
// 也可以用于声明变量，但是声明的空值变量，只能赋值为undefined或者null，前提是strictNullChecks为指定或者设置为false
let isVoid: void = undefined;

// null和undefined是所有类型的子类型，被定义为null或undefined的变量，可以赋值给其他类型
let isNull:null = null;
// let num: number = undefined; // 前提是strictNullChecks为指定或者设置为false
// let num:number = void; // void不能赋值给除undefined和null外的类型


/******************************************* */
// 任意值
// 一般来说，定义变量并指定类型后，在后续的使用过程中不能赋值为其他类型的值
// isStr = 10; // 编译出错，isStr在定义时被指定为string，不能在使用的过程中赋值为number类型
// 如果定义为any任意值类型，那么在使用时也可以更改类型。任意值就表示可以是任意类型，不建议大量使用any，这将失去ts的特性
let isAny:any = 10;
isAny = 'hello';
// 既然任意值是可以为任意类型，那么也就可以调用任意方法，当然这些方法是有意义的
// 如果在声明变量时，未指明类型，则默认未any类型
let anyThing; // 类型为any

// 类型推论
// 在定义变量时，虽然未指定类型，但是ts可通过初始值的类型推论出变量的类型
let num2 = 10;
// num2 = 'hello'; // num2推论出类型为number，赋值为string类型时会编译出错
// 如果定义变量时，未指定类型，也没有初始值，那么其类型将会被推论为any


/******************************************* */
// 联合类型
// 表示值的类型可以是多种类型中的一种，联合类型采用|分割不同的类型
let strAndNum: string | number;
strAndNum = 1;
strAndNum = 'hello';
// strAndNum = undefined; // 前提是strictNullChecks为指定或者设置为false
// strAndNum = false; // strAndNum可为string、number类型中的一种，但是不能为其他的

// 只能访问联合类型的所有类型中共有的属性和方法，因为不确定具体的类型
strAndNum.split(','); // 可能会报错，如果根据类型推论能得到strAndNum的类型为string则编译正常，否则会报错


/******************************************* */
// 对象的类型--接口
// 除了基本的数据类型，有时候我们会使用一些自定义的数据类型，这种时候就需要使用接口
// 接口是对行为的抽象，使用方式比较灵活
interface Person1 {
  name: string,
  age: number
}
let student: Person1 = {
  name: '张三',
  age: 18
}
// 在使用接口定义变量时，变量赋值时必须和接口保持一致，不能多一些属性，也不能少一些属性

// 可选属性
interface Person2 {
  name: string,
  age?: number
}
let student2: Person2 = {
  name: '张三',
}
// 可选属性的意思是可以不存在该属性，但是如果出现了未在接口中定义的属性，那也是不行的。例如
let student3: Person2 = {
  name: '张三',
  // sex: 'man', // 未在接口中定义的属性
}

// 任意属性
// 为了扩展，我们允许接口中存在任意属性
interface Person3 {
  name: string,
  age?: number,
  [propName: string]: any
}
let student4: Person3 = {
  name: '张三',
  sex: 'man'
}
// 需要注意的是，一旦定义了任意属性，那么确定属性和可选属性都必须是任意属性的子类型。因为相对任意属性来说，确定属性和可选属性都只是它的特例

// 只读属性
// 如果希望对象只能在创建时赋初始值，并且后续不可再更改，那么可以考虑使用只读属性
interface Person4 {
  name: string,
  age?: number,
  [propName: string]: any,
  readonly id: number
}
let student5: Person4 = {
  name: '张三',
  sex: 'man',
  id: 9527
}
// student5.id = 1234; // 无法修改只读属性


/******************************************* */
// 数组的类型
// 对象的类型我们一般采用接口的方式，数组的类型则比较灵活
// 使用类型+[]表示数组
let isNumberArr: number[] = [1,2,3];
// let isNumberArr: number[] = [1,2,'3']; // 数组中一般是单类型数据，不允许出现其他类型的数据

// 数组泛型，泛型中指定类型
let isArr: Array<number> = [1,2,3];

// 数组也是对象，可以使用接口表示数组
interface NumberArray {
  [index: number]: number
}
let isNumArr:NumberArray = [1,2,3];

// 类数组，不是普通的数组类型，但类似数组。一般来说，类数组会采用接口的方式定义
// index是number类型, 不会检测其他非number类型的key
interface IArgument {
  [index: number]: string,
  length: number,
  callee: Function,
}
function sum(x:number, y:number) {
  let args:IArgument = arguments
  console.log(arguments, args[0]);
}
sum(1, 2)

// 数组中一般是单类型数据，如果我们需要在数组中存在不同的数据类型，那么可以考虑任意类型数组
let isAnyArr: any[] = [1, '2', true];


/******************************************* */
// 函数的类型
// 函数定义的方式有两种：使用function声明或者采用函数表达式。如果需要使用ts对函数进行约束，那么其输入和输出都应该考虑到。
function sum1(x: number, y: number): number {
  return x + y;
} // function
let sum2 = (x: number, y: number): number => {
  return x + y;
} // 表达式

// 也可以使用接口来定义函数的形式
interface SumFuc {
  (x: number, y: number): number;
}
let sum3: SumFuc = function(x: number, y: number) {
  return x + y;
}

// 可选参数
// 类似于对象的可选属性，使用?来声明，值得注意的是可选参数后面不能再出现必需参数，并且可选参数在必需参数后面
function sum4(x: number, y: number, z?: number): number {
  if (z) {
    return x + y + z;
  }
  return x + y;
}

// 默认参数
// 可以给函数的参数添加默认值，ts会将添加了默认值的参数识别为可选参数，此时默认参数并没有位置要求，其后也可以跟必需参数
function sum5(x: number = 10, y: number, z?: number): number {
  if (z) {
    return x + y + z;
  }
  return x + y;
}

// 剩余参数
// 有时候参数很多，或者不确定有哪些参数，可以采用剩余参数的方式将这些参数都放在一个参数列表中
function sum6(x: number, ...items: number[]): number {
  items.forEach(element => {
    x+= element
  });
  return x;
}
console.log(sum6(1,2,3,4,5)); // 15

// 函数重载
// 如果一个函数接收不同的参数类型和参数数量，并作出不同的响应处理，那么这个函数就是重载函数。关键点在于不同的参数列表
// 例如我们想实现字符串数值、数字两者之间任意组合的相加
function add(x: number, y:number):number;
function add(x:number, y:string): number;
function add(x:string, y:number): number;
function add(x:string, y:string): number;
function add(x: number | string, y: number | string): number {
  if (isNaN(Number(x)) || isNaN(Number(y))) {
    return 0;
  }
  return Number(x) + Number(y);
}
console.log('add', add('1', 2));

